package com.anlogic.sdk.bootimage.internal.ui;

import com.anlogic.sdk.bootimage.internal.ui.common.IUIValidator;
import com.anlogic.sdk.bootimage.internal.ui.composite.BootGenMainComposite;
import com.anlogic.sdk.bootimage.internal.ui.composite.IBootgenUIContants;
import com.anlogic.sdk.bootimage.model.BootGenModel;
import com.anlogic.sdk.bootimage.model.Partition;
import com.anlogic.sdk.bootimage.model.options.AuthenticationType;
import com.anlogic.sdk.bootimage.model.options.PartionType;
import com.anlogic.sdk.utils.ECCUtils;
import com.anlogic.sdk.utils.FileUtils;
import com.anlogic.sdk.utils.StringUtils;
import com.anlogic.sdk.utils.options.ChipType;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.IEncodedStreamContentAccessor;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;

public class BootgenDialog extends TitleAreaDialog implements IBootgenUIContants {
	public static final String PREF_KEY_BOOTGEN_FILES = "bootgen_files_selection";

	private static final String BIF_FILE_PREFIX = "BIF File";

	private static final String IMPORT_BIF_FILE_PREFIX = "Import BIF File";

	protected BootGenMainComposite composite;

	protected BootGenModel model;

	protected Button previewButton;

	public BootgenDialog(Shell parentShell) {
		super(parentShell);
		setShellStyle(getShellStyle() | 0x10);
	}

	protected void configureShell(Shell newShell) {
		super.configureShell(newShell);
		newShell.setText("Create Boot Image");
		Monitor primary = Display.getCurrent().getPrimaryMonitor();
		Rectangle bounds = primary.getBounds();
		Rectangle rect = newShell.getBounds();
		int x = bounds.x + (bounds.width - rect.width) / 2;
		int y = bounds.y + (bounds.height - rect.height) / 2;
		newShell.setLocation(x, y);
	}

	protected Control createDialogArea(Composite parent) {
		setTitle("Create Boot Image");
		if (this.model == null)
			this.model = new BootGenModel();
		this.composite = createMainComposite(parent);
		this.composite.refreshCompleteUI();
		this.composite.addListeners();
		int horzHint = convertHorizontalDLUsToPixels(500);
		int vertHint = (int) ((Display.getDefault().getBounds()).height * 0.7D);
		GridDataFactory.fillDefaults().grab(true, true).hint(horzHint, vertHint).applyTo((Control) this.composite);
		this.composite.setUiValidator(new IUIValidator() {
			public void validate() {
				BootgenDialog.this.validateModel();
			}
		});
		Display.getDefault().asyncExec(new Runnable() {
			public void run() {
				BootgenDialog.this.validateModel();
			}
		});
		return (Control) this.composite;
	}

	protected BootGenMainComposite createMainComposite(Composite parent) {
		BootGenMainComposite bootGenComposite = new BootGenMainComposite(parent, this.model);
		return bootGenComposite;
	}

	public void validateModel() {
		if (this.composite.isDisposed())
			return;
		syncModel();
		setErrorMessage(null);
		setOkButtonEnabled(false);
		setMessage(null, 2);
		if (!this.model.isNew()) {
			checkFileExists(this.model.getImportBifFile(), IMPORT_BIF_FILE_PREFIX);
			if (getErrorMessage() != null)
				return;
			if (!this.model.getImportBifFile().endsWith(".bif"))
				setErrorMessage("Import BIF File extension should be bif");
			if (getErrorMessage() != null)
				return;
		}
		String bifFile = this.model.getBifFile();
		checkFileEmpty(bifFile, BIF_FILE_PREFIX);
		if (getErrorMessage() != null)
			return;
		if (!bifFile.endsWith(".bif"))
			setErrorMessage("BIF File extension should be bif");
		if (getErrorMessage() != null)
			return;
		if (!(new Path(bifFile)).isAbsolute())
			setErrorMessage("Please enter absolute path of BIF file");
		if (getErrorMessage() != null)
			return;
		if (this.model.isOverwriteBif())
			setMessage(
					"BIF file already exists at the specified path and will be overwritten with the modified contents. Use 'Preview Bif Changes' button to view the changes in bif contents before overwriting.",
					2);
		if (getErrorMessage() != null)
			return;
		if (this.model.isUseAuthentication()) {
			if (this.model.getPpkFile().trim().isEmpty() && this.model.getPskFile().trim().isEmpty()
					&& this.model.getSpkFile().trim().isEmpty() && this.model.getSskFile().trim().isEmpty()) {
				setErrorMessage("Please specify authentication keys");
				return;
			}
			if (this.model.isGenAuthKeys()) {
				if (isFileExist(this.model.getPpkFile()) || isFileExist(this.model.getPskFile())
						|| isFileExist(this.model.getSpkFile()) || isFileExist(this.model.getSskFile())) {
					setMessage("Authentication key file already exists at the specified path and will be overwritten with the modified contents.", 2);
				}
			}
			if (this.model.isGenSpkSig()) {
				if(isFileExist(this.model.getSpkSignFile())) {
					setMessage("SPK Signature file already exists at the specified path and will be overwritten with the modified contents.", 2);
				}
				if (!this.model.checkFilePathLegality(this.model.getSpkSignFile())) {
					setErrorMessage("Please specify correct SPK Signature file to generate spksignature!");
					return;
				}

				if (this.model.isGenAuthKeys()) {
					if (!this.model.checkFilePathLegality(this.model.getPskFile())) {
						setErrorMessage("Please specify correct PSK file to generate spksignature!");
						return;
					}
				} else {
					if (!this.model.checkFileExists(this.model.getPskFile())) {
						setErrorMessage("Please specify correct PSK file to generate spksignature!");
						return;
					}
				}
			}
			if (!this.model.checkAuthKeyLegality()) {
				setErrorMessage("Please specify correct authentication keys: PPK + SSK + SPK Signature or PSK + SSK");
				return;
			}
			if (this.model.isGenAuthKeys() && this.model.getAuthType().equals(AuthenticationType.NONE)) {
				setErrorMessage("Please select authentication type to generate authentication keys!");
				return;
			}
			if (ChipType.isDr1(this.model.getChipType().getName())) {
				String spkId = this.model.getSpkId().trim();
				if (StringUtils.isNotEmpty(spkId)) {
					try {
						Long.decode(spkId);
					} catch (NumberFormatException numberFormatException) {
						setErrorMessage("Invalid Spk ID. Specify a 32 bit integer or hexadecimal value.");
						return;
					}
				}
			}
		}
		if (this.model.isUseEncryption()) {
			if (!this.model.checkKeyFileLegality()) {
				setErrorMessage("Encryption key path is illegal.");
				return;
			}
			if (this.model.checkKeyFileExist()) {
				int goldLength = this.model.getEncryptionType().getLength();
				String keyHash = ECCUtils.getEnctriptKeyHashValue(this.model.getKeyFile());
				int keyLength = keyHash.length() / 2 * 8;
				if (goldLength != keyLength) {
					String msg = String.format("An %s key must be %s bits long, please check the key file", this.model.getEncryptionType().getName(), goldLength);
					setErrorMessage(msg);
					return;
				}
			}
		}
		if (this.model.getPartitions().size() == 0) {
			setErrorMessage("At least one partition should be present in bootimage");
			return;
		}
		int idx = 0;
		int bootloaderCount = 0;
		for (Partition partition : this.model.getPartitions()) {
			if (partition.getPartitionType() == PartionType.BOOTLOADER) {
				bootloaderCount++;
				if (bootloaderCount > 1) {
					setErrorMessage("Only one bootloader partition is allowed.");
				} else if (idx != 0) {
					setErrorMessage("Bootloader partition should be specified as the first partition.");
				}
			}
			checkFileExists(partition.getFilePath(), "Partition file \"" + partition.getFilePath() + "\"");
			if (getErrorMessage() != null)
				return;
			idx++;
		}
		if (getErrorMessage() != null)
			return;
		checkFileEmpty(this.model.getOutputFile(), "Output File");
		if (getErrorMessage() != null)
			return;
		String fileExtension = FileUtils.getFileExtension(this.model.getOutputFile());
		if (!this.model.isSplit() && !"MCS".equalsIgnoreCase(fileExtension) && !"BIN".equalsIgnoreCase(fileExtension))
			setErrorMessage("Output File extension should be either bin or mcs");
		if (StringUtils.isEmpty(getMessage())) {
			String message = "Creates %s Boot Image in .%s format from given FSBL elf and partition files in specified output folder.";
			setMessage(String.format(message,
					new Object[] { this.model.getChipType().getName(), this.model.getFormatType().toLowerCase() }));
		}
		if (getErrorMessage() == null)
			setOkButtonEnabled(true);
	}

	protected void checkFileEmpty(String file_path, String name) {
		if (file_path.isEmpty())
			setErrorMessage(String.valueOf(name) + " path is empty");
	}

	protected void checkFileExists(String file_path, String name) {
		if (file_path == null || file_path.isEmpty()) {
			setErrorMessage(String.valueOf(name) + " path is empty");
			return;
		}
		File file = this.model.getResolvedFile(file_path);
		if (file == null) {
			setErrorMessage(String.valueOf(name) + " path is empty");
			return;
		}
		if (!file.exists()) {
			setErrorMessage(String.valueOf(name) + " does not exist");
			return;
		}
		if (!file.isFile()) {
			setErrorMessage(String.valueOf(name) + " path is a directory");
			return;
		}
	}

	protected boolean isFileExist(String file_path) {
		File file = this.model.getResolvedFile(file_path);
		if (file == null)
			return false;
		if (!file.exists())
			return false;
		return true;
	}
	
	protected Control createButtonBar(Composite parent) {
		Control c = super.createButtonBar(parent);
		renameOkButton();
		setOkButtonEnabled(false);
		return c;
	}

	protected void createButtonsForButtonBar(Composite parent) {
		this.previewButton = createButton(parent, 22, "Preview BIF Changes", false);
		this.previewButton.addSelectionListener((SelectionListener) new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				BootgenDialog.this.previewChanges();
			}
		});
		super.createButtonsForButtonBar(parent);
	}

	protected void previewChanges() {
		syncModel();
		CompareConfiguration configuration = new CompareConfiguration();
		configuration.setLeftEditable(false);
		configuration.setRightEditable(false);
		final File outputBifFile = new File(this.model.getBifFile());
		configuration.setLeftLabel(outputBifFile.exists() ? outputBifFile.getAbsolutePath() : "Empty");
		configuration.setRightLabel(
				this.model.isOverwriteBif() ? (String.valueOf(outputBifFile.getAbsolutePath()) + " (modified)")
						: outputBifFile.getAbsolutePath());
		CompareEditorInput compareEditorInput = new CompareEditorInput(configuration) {
			protected Object prepareInput(IProgressMonitor monitor)
					throws InvocationTargetException, InterruptedException {
				BootgenDialog.DiffElement right = null;
				if (!outputBifFile.exists() || BootgenDialog.this.model.isEditBif()
						|| BootgenDialog.this.model.isNew()) {
					right = new BootgenDialog.DiffElement(BootgenDialog.this.model.getBifContent());
				} else {
					right = new BootgenDialog.DiffElement(outputBifFile);
				}
				return new DiffNode(new BootgenDialog.DiffElement(outputBifFile), right);
			}
		};
		CompareUI.openCompareDialog(compareEditorInput);
	}

	private class DiffElement implements ITypedElement, IEncodedStreamContentAccessor {
		private static final String UTF_16 = "UTF-16";

		private String content = "";

		private DiffElement(String content) {
			this.content = content;
		}

		private DiffElement(File file) {
			if (file.exists())
				this.content = FileUtils.read(file);
		}

		public String getName() {
			return "None";
		}

		public String getType() {
			return "txt";
		}

		public Image getImage() {
			return null;
		}

		public InputStream getContents() {
			return new ByteArrayInputStream(BootgenDialog.getBytes(this.content, UTF_16));
		}

		public String getCharset() {
			return UTF_16;
		}
	}

	private static byte[] getBytes(String s, String encoding) {
		byte[] bytes = null;
		if (s != null)
			try {
				bytes = s.getBytes(encoding);
			} catch (UnsupportedEncodingException unsupportedEncodingException) {
				bytes = s.getBytes();
			}
		return bytes;
	}

	protected void okPressed() {
		syncModel();
		File bootImageFile = this.model.getResolvedFile(this.model.getOutputFile());
		boolean bootImageExists = bootImageFile.exists();
		boolean overwriteBifFile = this.model.isOverwriteBif();
		String message = null;
		if (bootImageExists && overwriteBifFile) {
			message = "Output and BIF files already exist at the specified paths. Do you want to override them?";
		} else if (bootImageExists) {
			message = "Output file already exists at the specified path. Do you want to override it?";
		} else if (overwriteBifFile) {
			message = "BIF file already exists at the specified path. Do you want to override it?";
		}
		if (message != null) {
			boolean openConfirm = MessageDialog.openConfirm(Display.getCurrent().getActiveShell(), "Override Files",
					message);
			if (!openConfirm)
				return;
			this.model.setOverwrite(bootImageExists);
		}
		super.okPressed();
	}

	protected void syncModel() {
		this.model = this.composite.getModel();
	}

	private void renameOkButton() {
		Button okButton = getButton(0);
		if (okButton != null) {
			okButton.setText("Create Image");
			setButtonLayoutData(okButton);
		}
	}

	protected void setOkButtonEnabled(boolean en) {
		Button okButton = getButton(0);
		if (okButton != null)
			okButton.setEnabled(en);
	}

	public BootGenModel getModel() {
		return this.model;
	}

	public void setModel(BootGenModel model) {
		this.model = model;
	}
}
